﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Borg.Config;

namespace Borg
{
    public abstract class InitializerBase
    {
        protected readonly List<string> startupPaths = new List<string>();
        protected ApplicationConfig application = new ApplicationConfig();
        protected readonly List<string> loaderPaths = new List<string>();
        protected readonly Dictionary<string, string> arguments = new Dictionary<string, string>();

        protected VerbosityLevel verbosity = VerbosityLevel.Normal;
        protected readonly List<LoggerConfig> loggers = new List<LoggerConfig>();

        protected TypeConfig loader = new TypeConfig();
        protected TypeConfig dispatcher = new TypeConfig();
        protected TypeConfig qualifier = new TypeConfig();
        protected TypeConfig composer = new TypeConfig();
        protected TypeConfig activator = new TypeConfig();
        protected TypeConfig notifier = new TypeConfig();
        protected TypeConfig invoker = new TypeConfig();
        protected TypeConfig typeStore = new TypeConfig();
        protected TypeConfig instanceStore = new TypeConfig();
        protected TypeConfig settingStore = new TypeConfig();
        protected SettingProviderConfig settingProvider = new SettingProviderConfig();

        protected PersistorConfig persistor = new PersistorConfig();
        protected IList<AddOnTypeConfig> addOns = new List<AddOnTypeConfig>();
        protected bool createAppDomain;

        public bool CreateAppDomain
        {
            get { return createAppDomain; }
            set { createAppDomain = value; }
        }

        public ApplicationConfig Application
        {
            get { return application ?? (application = new ApplicationConfig()); }
            set { application = value; }
        }

        public List<string> LoaderPaths
        {
            get { return loaderPaths; }
        }

        public List<string> StartupPaths
        {
            get { return startupPaths; }
        }

        public Dictionary<string, string> Arguments
        {
            get { return arguments; }
        }

        public VerbosityLevel Verbosity
        {
            get { return verbosity; }
            set { verbosity = value; }
        }

        public List<LoggerConfig> Loggers
        {
            get { return loggers; }
        }

        public TypeConfig Loader
        {
            get { return loader; }
            set { loader = value; }
        }

        public TypeConfig Dispatcher
        {
            get { return dispatcher; }
            set { dispatcher = value; }
        }

        public TypeConfig Qualifier
        {
            get { return qualifier; }
            set { qualifier = value; }
        }

        public TypeConfig Composer
        {
            get { return composer; }
            set { composer = value; }
        }

        public TypeConfig Activator
        {
            get { return activator; }
            set { activator = value; }
        }

        public TypeConfig Notifier
        {
            get { return notifier; }
            set { notifier = value; }
        }

        public TypeConfig Invoker
        {
            get { return invoker; }
            set { invoker = value; }
        }

        public TypeConfig TypeStore
        {
            get { return typeStore; }
            set { typeStore = value; }
        }

        public TypeConfig InstanceStore
        {
            get { return instanceStore; }
            set { instanceStore = value; }
        }

        public TypeConfig SettingStore
        {
            get { return settingStore; }
            set { settingStore = value; }
        }

        public SettingProviderConfig SettingProvider
        {
            get { return settingProvider; }
            set { settingProvider = value; }
        }

        public PersistorConfig Persistor
        {
            get { return persistor; }
            set { persistor = value; }
        }

        public IList<AddOnTypeConfig> AddOns
        {
            get { return addOns; }
            set { addOns = value; }
        }

        public void ApplyAssemblyInfo(Assembly assembly)
        {
            var productAttrs = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
            var companyAttrs = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
            var infoVerAttrs = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);

            if (productAttrs.Length > 0)
                Application.Name = ((AssemblyProductAttribute)productAttrs[0]).Product;
            
            if (companyAttrs.Length > 0)
                Application.Company = ((AssemblyCompanyAttribute)companyAttrs[0]).Company;
            
            if (infoVerAttrs.Length <= 0)
                return;
            
            Application.Version = ((AssemblyInformationalVersionAttribute)infoVerAttrs[0]).InformationalVersion;
        }

        public void ApplyConfig(Config.ConfigurationSection configSection)
        {
            if (configSection.CreateAppDomain.Value.HasValue)
                createAppDomain = configSection.CreateAppDomain.Value.Value;
            
            ApplyApplication(configSection.Application);
            ApplyLoaderPaths(configSection.LoaderPath);
            ApplyStartupPaths(configSection.StartupPath);
            ApplyArguments(configSection.Arguments);
            ApplyRuntime(configSection.Runtime);
        }

        protected virtual RuntimeConfig CreateRuntimeConfig(string user, List<LogArgs> logs)
        {
            return new RuntimeConfig
            {
                User = user,
                EnvironmentUri = GetEnvironmentUri(user),
                Application = application,
                LoaderDirectories = GetLoaderDirectories(user, logs),
                Arguments = GetArguments(logs),
                Loggers = loggers.ToArray(),
                Verbosity = verbosity,
                Loader = loader,
                Dispatcher = dispatcher,
                Qualifier = qualifier,
                Composer = composer,
                Activator = activator,
                Notifier = notifier,
                Invoker = invoker,
                TypeStore = typeStore,
                InstanceStore = instanceStore,
                SettingStore = settingStore,
                SettingProvider = settingProvider,
                Persistor = persistor,
                AddOns = addOns
            };
        }

        protected virtual Runtime InternalCreateRuntime(string user, out RuntimeInternals runtimeInternals)
        {
            var runtime = InternalCreateRuntime(user);

            runtimeInternals = new RuntimeInternals(runtime);

            return runtime;
        }

        protected virtual Runtime InternalCreateRuntime(string user)
        {
            var logs = new List<LogArgs>();
            var currentDomain = AppDomain.CurrentDomain;
            var builder = new StringBuilder(currentDomain.SetupInformation.PrivateBinPath);
            var runtimeConfig = CreateRuntimeConfig(user, logs);

            Runtime runtime;

            if (createAppDomain)
            {
                foreach (var directory in runtimeConfig.LoaderDirectories)
                {
                    builder.Append(";");
                    builder.Append(directory);
                }

                var appDomainSetup = new AppDomainSetup
                {
                    ShadowCopyFiles = "true",
                    PrivateBinPath = currentDomain.SetupInformation.PrivateBinPath,
                    PrivateBinPathProbe = currentDomain.SetupInformation.PrivateBinPathProbe,
                    ShadowCopyDirectories = builder.ToString(),
                    ApplicationName = currentDomain.SetupInformation.ApplicationName,
                    ApplicationTrust = currentDomain.ApplicationTrust,
                    ApplicationBase = currentDomain.BaseDirectory,
                    DynamicBase = currentDomain.DynamicDirectory
                };

                var appDomain =
                    AppDomain.CreateDomain(
                        string.Format(
                            "{0}.borg",
                            user ?? "default"),
                        currentDomain.Evidence,
                        appDomainSetup);

                runtime =
                    (Runtime)appDomain.CreateInstance(
                    typeof(Runtime).Assembly.FullName,
                    typeof(Runtime).FullName,
                    false,
                    BindingFlags.Instance | BindingFlags.NonPublic,
                    null,
                    new object[] { runtimeConfig },
                    null,
                    null).Unwrap();
            }
            else
            {
                currentDomain.SetShadowCopyFiles();
                runtime = new Runtime(runtimeConfig);
            }

            foreach (var log in logs)
                runtime.Logger.Log(log);

            return runtime;
        }

        protected virtual Uri GetEnvironmentUri(string user)
        {
            return new UriBuilder("borg", Environment.UserDomainName) { UserName = user }.Uri;
        }

        protected virtual string GetFullPath(string path)
        {
            if (!Path.IsPathRooted(path))
                path = PathHelper.GetFullPath(path);
            
            if (path.IndexOf("{application}", StringComparison.Ordinal) >= 0)
            {
                if (application == null || string.IsNullOrEmpty(application.Name))
                    return null;

                path = path.Replace("{application}", application.Name);
            }

            return path;
        }

        protected virtual string[] GetLoaderDirectories(string user, List<LogArgs> logs)
        {
            if (loaderPaths.Count != 0)
                return loaderPaths.ToArray();

            var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
            var directories = Directory.GetDirectories(baseDirectory, "*", SearchOption.AllDirectories);
            var dirs = new string[directories.Length + 1];

            dirs[0] = baseDirectory;

            Array.Copy(directories, 0, dirs, 1, directories.Length);
            
            return dirs;
        }

        protected virtual KeyValuePair<string, string>[] GetArguments(List<LogArgs> logs)
        {
            var arg = new KeyValuePair<string, string>[arguments.Count];
            var i = 0;
            
            foreach (var argument in Arguments)
                arg[i++] = argument;

            return arg;
        }

        private void ApplyApplication(ApplicationElement applicationElement)
        {
            if (!string.IsNullOrEmpty(applicationElement.Name))
                Application.Name = applicationElement.Name;
            
            if (!string.IsNullOrEmpty(applicationElement.Company))
                Application.Company = applicationElement.Company;
            
            if (string.IsNullOrEmpty(applicationElement.Version))
                return;
            
            Application.Version = applicationElement.Version;
        }

        private void ApplyLoaderPaths(LoaderPathCollection pathElements)
        {
            if (pathElements.Count == 0)
                return;
            
            foreach (PathElement pathElement in pathElements)
                loaderPaths.Add(pathElement.Path);
        }

        private void ApplyStartupPaths(PathElementCollection pathElements)
        {
            if (pathElements.Count == 0)
                return;

            foreach (PathElement pathElement in pathElements)
                startupPaths.Add(pathElement.Path);
        }

        private void ApplyArguments(KeyValueConfigurationCollection argumentElements)
        {
            if (argumentElements.Count == 0)
                return;

            foreach (KeyValueConfigurationElement configurationElement in argumentElements)
                Arguments.Add(configurationElement.Key, configurationElement.Value);
        }

        private void ApplyRuntime(Config.RuntimeElement runtimeElement)
        {
            if (!string.IsNullOrEmpty(runtimeElement.Dispatcher.Type))
                dispatcher = new TypeConfig(runtimeElement.Dispatcher.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Loader.Type))
                loader = new TypeConfig(runtimeElement.Loader.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Qualifier.Type))
                qualifier = new TypeConfig(runtimeElement.Qualifier.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Composer.Type))
                composer = new TypeConfig(runtimeElement.Composer.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Activator.Type))
                activator = new TypeConfig(runtimeElement.Activator.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Notifier.Type))
                notifier = new TypeConfig(runtimeElement.Notifier.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.Invoker.Type))
                invoker = new TypeConfig(runtimeElement.Invoker.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.TypeStore.Type))
                typeStore = new TypeConfig(runtimeElement.TypeStore.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.InstanceStore.Type))
                instanceStore = new TypeConfig(runtimeElement.InstanceStore.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.SettingStore.Type))
                settingStore = new TypeConfig(runtimeElement.SettingStore.Type);
            
            if (!string.IsNullOrEmpty(runtimeElement.SettingProvider.Type) ||
                !string.IsNullOrEmpty(runtimeElement.SettingProvider.Path))
                settingProvider = new SettingProviderConfig(
                    runtimeElement.SettingProvider.Type, runtimeElement.SettingProvider.Path);
            
            if (!string.IsNullOrEmpty(runtimeElement.Persistor.Type) ||
                !string.IsNullOrEmpty(runtimeElement.Persistor.Path))
                persistor = new PersistorConfig(
                    runtimeElement.Persistor.Type, runtimeElement.Persistor.Path);

            foreach (var addOnElement in runtimeElement.AddOns.Cast<AddOnElement>().
                Where(addOnElement => !string.IsNullOrEmpty(addOnElement.Type)))
                addOns.Add(new AddOnTypeConfig(
                    addOnElement.Type, GetParameters(addOnElement.Parameters)));

            ApplyLoggers(runtimeElement.Loggers);

            if (!runtimeElement.Verbosity.HasValue)
                return;
            
            verbosity = runtimeElement.Verbosity.Value;
        }

        private Dictionary<string, string> GetParameters(KeyValueConfigurationCollection argumentElements)
        {
            return argumentElements.Cast<KeyValueConfigurationElement>().
                ToDictionary(configurationElement =>
                    configurationElement.Key, configurationElement =>
                        configurationElement.Value);
        }

        private void ApplyLoggers(LoggerElementCollection loggerElements)
        {
            if (loggerElements.Count == 0)
                return;
            
            foreach (LoggerElement loggerElement in loggerElements)
                loggers.Add(new LoggerConfig(loggerElement.Type, loggerElement.Path));
        }
    }
}
